/* vim:set sw=8 et ai cin: */
//----------------------------------------------------------------------//
// Header files								//
//----------------------------------------------------------------------//

//#include <linux/config.h>
//#include <linux/autoconf.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/device.h>

#include <linux/kernel.h>	/* printk() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/fs.h>		/* everything... */
#include <linux/errno.h>	/* error codes */
#include <linux/types.h>	/* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>	/* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <linux/workqueue.h>

#include <asm/system.h>		/* cli(), *_flags */
#include <asm/uaccess.h>	/* copy_*_user */

#include <asm/io.h>
#include <asm/irq.h>
#include <asm/blackfin.h>
#include <asm/dma.h>
#include <linux/spi/spi.h>
#include <asm/cacheflush.h>

#include "cbfxs.h"


#include <linux/version.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/sound.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/smp_lock.h>
#include <asm/uaccess.h>
#include <asm/hardirq.h>

DECLARE_MUTEX(ioctl_spi_mutex);
DECLARE_COMPLETION(spi_command_completion);

static int channel_to_work = 0;

struct cbfxs_dev {
   struct semaphore sem;     /* mutual exclusion semaphore     */
   struct cdev cdev;         /* Char device structure              */
   struct spi_device *spidev;
   wait_queue_head_t inq, outq;       /* read and write queues */

   struct cbfxs_ioctl_spi_command command;
};

static struct cbfxs_dev *cbfxs_devices;	/* allocated in cbfxs_init_module */

#define SPI_CHANNELS 7
//static int spi_fxs_channels[SPI_CHANNELS] = {PF2, PF3, PF4, PF5, PF6, PF7, PF1};

static inline int MINMAX(int v, int min, int max)
{
	if(v < min) return min;
	if(v > max) return max;
	return v;
}

int cbfxs_spi_send_and_receive(struct cbfxs_dev* dev)
{
        struct spi_transfer t;
        struct spi_message m;

	if(dev->command.buffer_sz < 1) return -1;
	if(dev->command.buffer_sz >= CBFXS_SPI_BUFFER_SIZE) return -1;

        spi_message_init(&m);
        memset(&t, 0, sizeof(t));
        t.rx_buf = dev->command.buffer;
        t.tx_buf = dev->command.buffer;
        t.len = dev->command.buffer_sz;

        spi_message_add_tail(&t, &m);
        return spi_sync(dev->spidev, &m);
}

static int cbfxs_spi_init(struct cbfxs_dev *dev)
{
#if 0
	dev->spi.dev_name = CBFXS_DEVNAME;
	dev->spi.bdrate = 16;
	dev->spi.dma = 0;
	dev->spi.flag = 0;
	dev->spi.irq_handler = cbfxs_spi_irq_handler;
	dev->spi.priv_data = dev;
	dev->spi.size = CFG_SPI_WORDSIZE8;
	dev->spi.master = CFG_SPI_MASTER;
	dev->spi.enable = CFG_SPI_ENABLE;
	dev->spi.send_zero = 1;
	dev->spi.ti_mod = BIT_CTL_TXMOD; // interruption when data can go
#endif
	return 0;
}

static int cbfxs_spi_done(struct cbfxs_dev *dev)
{
	return 0;
}


/*
 * Legerity Serial - DMA and Sport0
 */

// SPI transfer mode
#define TIMOD_DMA_TX 0x0003

// DMA flow mode
#define FLOW_1	0x1000

#define LEGERITY_NUM_CHANNELS 16
#define LEGERITY_SERIAL_NUM_BLOCKS 2 
#define LEGERITY_SERIAL_BLOCK_SIZE (8*LEGERITY_NUM_CHANNELS)
#define LEGERITY_TIMESLOTS ((1<<LEGERITY_NUM_CHANNELS)-1)
#define AUDIO_BLOCKS_BUFFER 20

static struct stAudioBlocks {
   unsigned char buffer[AUDIO_BLOCKS_BUFFER][LEGERITY_SERIAL_BLOCK_SIZE];
   int read_pos, write_pos;
} audio_blocks_rx = {{},0,0}, audio_blocks_tx = {{},0,0};

static int audioblocks_has_data(struct stAudioBlocks *audioblocks)
{
	unsigned long flags;
	int res;
	local_irq_save(flags);
	res = audioblocks->read_pos == audioblocks->write_pos ? 0 : -1;
	local_irq_restore(flags);
	return res;
}

static int audioblocks_has_space(struct stAudioBlocks *audioblocks)
{
	return -1; // always has space
#if 0
	unsigned long flags;
	int next_write_pos, res = -1;
	local_irq_save(flags);
	next_write_pos = audioblocks->write_pos+1;
	if(next_write_pos >= AUDIO_BLOCKS_BUFFER)
		next_write_pos = 0;
	if(audioblocks->read_pos == next_write_pos) res = 0;
	local_irq_restore(flags);
	return res;
#endif
}

static int audioblocks_get(struct stAudioBlocks *audioblocks, 
				unsigned char *dest_buf)
{
	unsigned long flags;
	int res = -1;
	local_irq_save(flags);
	if(audioblocks->read_pos == audioblocks->write_pos) goto end;
	memcpy(dest_buf, audioblocks->buffer[audioblocks->read_pos++], 
	      LEGERITY_SERIAL_BLOCK_SIZE);
	if(audioblocks->read_pos >= AUDIO_BLOCKS_BUFFER)
		audioblocks->read_pos = 0;
	res = 0;
end:
	local_irq_restore(flags);
	return res;
}

static int audioblocks_put(struct stAudioBlocks *audioblocks, 
				unsigned char *buf)
{
	unsigned long flags;
	local_irq_save(flags);
	memcpy(audioblocks->buffer[audioblocks->write_pos++], buf,
	      LEGERITY_SERIAL_BLOCK_SIZE);
	if(audioblocks->write_pos >= AUDIO_BLOCKS_BUFFER)
		audioblocks->write_pos = 0;
	if(audioblocks->read_pos == audioblocks->write_pos) {
	   // full buffer, destroy older one
		int i;
		for(i = 0; i < AUDIO_BLOCKS_BUFFER-3; i++) {
			audioblocks->read_pos++;
			if(audioblocks->read_pos >= AUDIO_BLOCKS_BUFFER)
				audioblocks->read_pos = 0;
		}
	}
	local_irq_restore(flags);
	return 0;
}

// SPORT0 DMA transmit buffer
static volatile unsigned char iTxBuffer1[LEGERITY_SERIAL_NUM_BLOCKS*LEGERITY_SERIAL_BLOCK_SIZE];
// SPORT0 DMA receive buffer
static volatile unsigned char iRxBuffer1[LEGERITY_SERIAL_NUM_BLOCKS*LEGERITY_SERIAL_BLOCK_SIZE];

static irqreturn_t Sport0_RX_ISR(int irq, void *dev_id);

//----------------------------------------------------------------------//
// Function:	Init_Sport0						//
// Description:	Configure Sport0 for TDM mode, to transmit/receive data //
//	to/from the Legerity. Configure Sport for internal clocks and 	//
//				frame syncs.				//
//----------------------------------------------------------------------//
static void Init_Sport0(void)
{
	// Sport0 receive configuration
	// Internal CLK, Internal Frame sync active H, MSB first
	// 8-bit data
	bfin_write_SPORT0_RCR1(0x0);
	bfin_write_SPORT0_TCR1(0x0);

//	bfin_write_SPORT0_RCR1(0x4A02);
	bfin_write_SPORT0_RCR1(0x0A02); //edge
	bfin_write_SPORT0_RCR2(0x07);
	
	// Sport0 transmit configuration
	// External CLK, External Frame sync active H, MSB first
	// 8-bit data
//	bfin_write_SPORT0_TCR1(0x4A02);
	bfin_write_SPORT0_TCR1(0x0A02); //edge
//	bfin_write_SPORT0_TCR1(0x4A12);  //LSB first
	bfin_write_SPORT0_TCR2(0x07);
	
	// Enable MCM 8 transmit & receive channels
	bfin_write_SPORT0_MTCS0(LEGERITY_TIMESLOTS); // Enable Timeslots
	bfin_write_SPORT0_MRCS0(LEGERITY_TIMESLOTS);

	bfin_write_SPORT0_MTCS1(0);
	bfin_write_SPORT0_MRCS1(0);
	bfin_write_SPORT0_MTCS2(0); 
	bfin_write_SPORT0_MRCS2(0);
	bfin_write_SPORT0_MTCS3(0);
	bfin_write_SPORT0_MRCS3(0);
	
	// Set MCM configuration register and enable MCM mode
	bfin_write_SPORT0_MCMC1(0x1000);	// Window Size 128
//	bfin_write_SPORT0_MCMC2(0x001c);	// 8 MHz
	bfin_write_SPORT0_MCMC2(0x009c);	// 8 MHz, H100
	bfin_write_SPORT0_TCLKDIV(0x4);	// DIVIDE 133 MHz/16 8 MHz
	bfin_write_SPORT0_RCLKDIV(0x4);	// DIVIDE 133 MHz/16 8 MHz
	bfin_write_SPORT0_TFSDIV(1023);	// 8 KHz
	bfin_write_SPORT0_RFSDIV(1023);	// 8 KHz
}


//----------------------------------------------------------------------//
// Function:	Init_DMA						//
//									//
// Description:	Initialize DMA1 in autobuffer mode to receive and DMA2	//
// in autobuffer mode to transmit					//
//----------------------------------------------------------------------//
static void Init_DMA(void)
{
	// Set up DMA1 to receive
	// Map DMA1 to Sport0 RX
	bfin_write_DMA1_PERIPHERAL_MAP(0x1000);
	
	// Configure DMA1
	// 8-bit transfers, Interrupt on completion, Autobuffer mode
	bfin_write_DMA1_CONFIG(WNR | WDSIZE_8 | DI_EN | FLOW_1 | DI_SEL | DMA2D); // dont enable dma as yet
	// Start address of data buffer
	bfin_write_DMA1_START_ADDR(iRxBuffer1);
	// DMA inner loop count
	bfin_write_DMA1_X_COUNT(LEGERITY_SERIAL_BLOCK_SIZE);
	// Inner loop address increment
	bfin_write_DMA1_X_MODIFY(1);
	bfin_write_DMA1_Y_MODIFY(1);
	bfin_write_DMA1_Y_COUNT(LEGERITY_SERIAL_NUM_BLOCKS);
	
	
	// Set up DMA2 to transmit
	// Map DMA2 to Sport0 TX
	bfin_write_DMA2_PERIPHERAL_MAP(0x2000);
	
	// Configure DMA2
	// 8-bit transfers, Autobuffer mode
	bfin_write_DMA2_CONFIG(WDSIZE_8 | FLOW_1 | DMA2D |DI_SEL);
	// Start address of data buffer
	bfin_write_DMA2_START_ADDR(iTxBuffer1);
	// DMA inner loop count
	bfin_write_DMA2_X_COUNT(LEGERITY_SERIAL_BLOCK_SIZE);
	// Inner loop address increment
	bfin_write_DMA2_X_MODIFY(1);
	bfin_write_DMA2_Y_MODIFY(1);
	bfin_write_DMA2_Y_COUNT(LEGERITY_SERIAL_NUM_BLOCKS);
}


//----------------------------------------------------------------------//
// Function:	Init_Interrupts						//
//									//
// Description:	Initialize Interrupt for Sport0 RX			//
//----------------------------------------------------------------------//
static int Init_Sport_Interrupts(void)
{
  	if(request_irq(IRQ_SPORT0_RX, Sport0_RX_ISR, 
		0, "Sport Legerity", NULL) != 0)
    		return -EBUSY;

	// enable Sport0 RX interrupt
	bfin_write_SIC_IMASK(bfin_read_SIC_IMASK() | 0x00000200);
	return 0;
}

static int Stop_Sport_Interrupts(void)
{
	free_irq(IRQ_SPORT0_RX, NULL);
	// disable Sport0 RX interrupt
	bfin_write_SIC_IMASK(bfin_read_SIC_IMASK() & ~0x00000200);
	return 0;
}

static void Enable_DMA_Sport0(void)
{
	// enable DMAs
	bfin_write_DMA2_CONFIG(bfin_read_DMA2_CONFIG() | DMAEN);
	bfin_write_DMA1_CONFIG(bfin_read_DMA1_CONFIG() | DMAEN);
	
	// enable Sport0 TX and RX
	bfin_write_SPORT0_TCR1(bfin_read_SPORT0_TCR1() | TSPEN);
	bfin_write_SPORT0_RCR1(bfin_read_SPORT0_RCR1() | RSPEN);
}

static void Disable_DMA_Sport0(void)
{
	// enable DMAs
	bfin_write_DMA2_CONFIG(bfin_read_DMA2_CONFIG() & ~DMAEN);
	bfin_write_DMA1_CONFIG(bfin_read_DMA1_CONFIG() & ~DMAEN);
	
	// enable Sport0 TX and RX
	bfin_write_SPORT0_TCR1(bfin_read_SPORT0_TCR1() & ~TSPEN);
	bfin_write_SPORT0_RCR1(bfin_read_SPORT0_RCR1() & ~RSPEN);
}

static int cbfxs_initialize_serial_legerity(void)
{
	int err;
	Init_Sport0();
	Init_DMA();
	err = Init_Sport_Interrupts();
	if(err) return err;
	Enable_DMA_Sport0();
	return 0;
}

static int cbfxs_stop_serial_legerity(void)
{
	Stop_Sport_Interrupts();
	Disable_DMA_Sport0();
	return 0;
}

/***** ISR ****/
static irqreturn_t Sport0_RX_ISR(int irq, void *dev_id)
{
	int work_serial_block;
	static int last_work_serial_block = -1;
	static int no_audio_indicated = 0;
	void *work_dma_buffer_tx, *work_dma_buffer_rx;
	// confirm interrupt handling
	bfin_write_DMA1_IRQ_STATUS(0x0001);

	// hack to interpret the page that dma is not working
	if(bfin_read_DMA1_CURR_X_COUNT() == 0)
		work_serial_block =  1;
	else
		work_serial_block = bfin_read_DMA1_CURR_Y_COUNT() == 2 ? 1 : 0;
	if(work_serial_block == last_work_serial_block) {
		printk(KERN_NOTICE "Lost DMA interrupt\n");
	}
	last_work_serial_block = work_serial_block;

	work_dma_buffer_tx = (void*)(iTxBuffer1 +
		work_serial_block*LEGERITY_SERIAL_BLOCK_SIZE);
	if(audioblocks_get(&audio_blocks_tx, work_dma_buffer_tx) < 0) {
		memset(work_dma_buffer_tx, 0x7F, LEGERITY_SERIAL_BLOCK_SIZE);
		if(no_audio_indicated == 0) {
			printk(KERN_NOTICE "No audio\n");
			no_audio_indicated = -1;
			last_work_serial_block = -1;
		}
	} else {
		no_audio_indicated = 0;
	}
	flush_dcache_range((unsigned int)work_dma_buffer_tx, 
		(unsigned int)work_dma_buffer_tx+LEGERITY_SERIAL_BLOCK_SIZE);

	work_dma_buffer_rx = (void*)(iRxBuffer1 +
		work_serial_block*LEGERITY_SERIAL_BLOCK_SIZE);
	invalidate_dcache_range((unsigned int)work_dma_buffer_rx, 
		(unsigned int)work_dma_buffer_rx+LEGERITY_SERIAL_BLOCK_SIZE);
	audioblocks_put(&audio_blocks_rx, work_dma_buffer_rx);

	wake_up_interruptible(&cbfxs_devices[0].inq);
	wake_up_interruptible(&cbfxs_devices[0].outq);

  /* we are using autobuffer mode in 2-d.
     There is no need to reinit the DMA.
  */
	return IRQ_HANDLED;
}

/*
 * Open and close
 */

int cbfxs_open(struct inode *inode, struct file *filp)
{
	struct cbfxs_dev *dev; /* device information */

	dev = container_of(inode->i_cdev, struct cbfxs_dev, cdev);
	filp->private_data = dev; /* for other methods */

//	printk(KERN_NOTICE "cbfxs_open "__DATE__" "__TIME__"\n");

	return 0;          /* success */
}

int cbfxs_release(struct inode *inode, struct file *filp)
{
//	printk(KERN_NOTICE "cbfxs_close\n");
	return 0;
}

/*
 * Data management: read and write
 */

ssize_t cbfxs_read(struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{
	struct cbfxs_dev *dev = filp->private_data; 
	ssize_t retval = 0;
	unsigned char copybuffer[LEGERITY_SERIAL_BLOCK_SIZE];

	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	while(!audioblocks_has_data(&audio_blocks_rx)) {
		up(&dev->sem);
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		if (wait_event_interruptible(dev->inq, 
			 audioblocks_has_data(&audio_blocks_rx)))
			return -ERESTARTSYS;
		if (down_interruptible(&dev->sem))
			return -ERESTARTSYS;
	}

	audioblocks_get(&audio_blocks_rx, copybuffer);
	if (copy_to_user(buf, copybuffer, LEGERITY_SERIAL_BLOCK_SIZE)) {
		retval = -EFAULT;
		goto out;
	}
	retval = LEGERITY_SERIAL_BLOCK_SIZE;

  out:
	up(&dev->sem);
	return retval;
}

ssize_t cbfxs_write(struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
	struct cbfxs_dev *dev = filp->private_data;
	ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
	unsigned char copybuffer[LEGERITY_SERIAL_BLOCK_SIZE];

	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	while(!audioblocks_has_space(&audio_blocks_tx)) {
		up(&dev->sem);
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		if (wait_event_interruptible(dev->outq, 
			 audioblocks_has_space(&audio_blocks_tx)))
			return -ERESTARTSYS;
		if (down_interruptible(&dev->sem))
			return -ERESTARTSYS;
	}

	if(count > LEGERITY_SERIAL_BLOCK_SIZE)
		count = LEGERITY_SERIAL_BLOCK_SIZE;
	if (copy_from_user(copybuffer, (void*)buf, count)) {
		retval = -EFAULT;
		goto out;
	}
	audioblocks_put(&audio_blocks_tx, copybuffer);
	retval = count;

  out:
	up(&dev->sem);
	return retval;
}

static unsigned int cbfxs_poll(struct file *filp, poll_table *wait)
{
	struct cbfxs_dev *dev = filp->private_data;
	unsigned int mask = 0;

	/*
	* The buffer is circular; it is considered full
	* if "wp" is right behind "rp" and empty if the
	* two are equal.
	*/
	down(&dev->sem);
	poll_wait(filp, &dev->inq,  wait);
	poll_wait(filp, &dev->outq, wait);
	if(audioblocks_has_data(&audio_blocks_rx))
		mask |= POLLIN | POLLRDNORM;    /* readable */
	if(audioblocks_has_space(&audio_blocks_tx))
		mask |= POLLOUT | POLLWRNORM;   /* writable */
	up(&dev->sem);
	return mask;
}


/*
 * The ioctl() implementation
 */

int cbfxs_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{
	struct cbfxs_dev *dev = filp->private_data;
	int err = 0;
	int retval = 0;
    
	/*
	 * extract the type and number bitfields, and don't decode
	 * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
	 */
	if (_IOC_TYPE(cmd) != CBFXS_IOC_MAGIC) return -ENOTTY;
	if (_IOC_NR(cmd) > CBFXS_IOC_MAXNR) return -ENOTTY;

	/*
	 * the direction is a bitmask, and VERIFY_WRITE catches R/W
	 * transfers. `Type' is user-oriented, while
	 * access_ok is kernel-oriented, so the concept of "read" and
	 * "write" is reversed
	 */
	if (_IOC_DIR(cmd) & _IOC_READ)
		err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
	else if (_IOC_DIR(cmd) & _IOC_WRITE)
		err =  !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
	if (err) return -EFAULT;

	switch(cmd) {
	  case CBFXS_IOC_SPI_CHANNEL:
		if (! capable (CAP_SYS_ADMIN))
                        return -EPERM;
		channel_to_work = arg;
                break;

	  case CBFXS_IOC_SPI_COMMAND:
		if (down_interruptible(&ioctl_spi_mutex))
			return -ERESTARTSYS;
		if (copy_from_user(&dev->command, (void*)arg, 
			 sizeof(dev->command))) {
			retval = -EFAULT;
			goto endspi;
		}
		if(cbfxs_spi_send_and_receive(dev)) {
			retval = -EFAULT;
			goto endspi;
		}
		if (copy_to_user((void*)arg, &dev->command, 
			 sizeof(dev->command))) {
			retval = -EFAULT;
			goto endspi;
		}
endspi:
		up(&ioctl_spi_mutex);
                break;

	  default:  /* redundant, as cmd was checked against MAXNR */
		return -ENOTTY;
	}
	return retval;
}

struct file_operations cbfxs_fops = {
	.owner =    THIS_MODULE,
	.read =     cbfxs_read,
	.write =    cbfxs_write,
	.poll =    cbfxs_poll,
	.ioctl =    cbfxs_ioctl,
	.open =     cbfxs_open,
	.release =  cbfxs_release,
};

static int __devinit cbfxs_spi_probe(struct spi_device *spi)
{
        cbfxs_devices[0].spidev = spi;
        return 0;
}

static int __devexit cbfxs_spi_remove(struct spi_device *spi)
{
        printk(KERN_ALERT "%s: Goodbye SPI\n", CBFXS_DEVNAME);
        return 0;
}

static struct spi_driver cbfxs_spi_driver = {
        .driver = {
                .name   = CBFXS_DEVNAME,
                .bus    = &spi_bus_type,
                .owner  = THIS_MODULE,
        },
        .probe  = cbfxs_spi_probe,
        .remove = __devexit_p(cbfxs_spi_remove),
};

/*
 * Finally, the module stuff
 */

void cbfxs_exit(void);

int cbfxs_init(void)
{
	int result, i;
	dev_t dev = 0;

        dev = MKDEV(CBFXS_MAJOR, 0);
        result = register_chrdev_region(dev, 1, CBFXS_DEVNAME);

	if (result < 0) {
		printk(KERN_WARNING "%s: can't get major %d\n", CBFXS_DEVNAME, CBFXS_MAJOR);
		return result;
	}

        /* 
	 * allocate the devices
	 */
	cbfxs_devices = kmalloc(1 * sizeof(struct cbfxs_dev), GFP_KERNEL);
	if (!cbfxs_devices) {
		result = -ENOMEM;
		goto fail;  /* Make this more graceful */
	}
	memset(cbfxs_devices, 0, 1 * sizeof(struct cbfxs_dev));

        /* Initialize each device. */
	for (i = 0; i < 1; i++) {
		init_waitqueue_head(&cbfxs_devices[i].inq);
		init_waitqueue_head(&cbfxs_devices[i].outq);
		init_MUTEX(&cbfxs_devices[i].sem);

                cdev_init(&cbfxs_devices[i].cdev, &cbfxs_fops);
                cbfxs_devices[i].cdev.owner = THIS_MODULE;
                cbfxs_devices[i].cdev.ops = &cbfxs_fops;
	}

	if(cbfxs_spi_init(&cbfxs_devices[0]) < 0) {
		result = -ENOMEM;
		goto fail;  /* Make this more graceful */
	}

	if(spi_register_driver(&cbfxs_spi_driver) < 0) {
		result = -ENOMEM;
		goto fail;  /* Make this more graceful */
	}

	if(cbfxs_initialize_serial_legerity() < 0) {
		result = -ENOMEM;
		goto fail;  /* Make this more graceful */
	}

	for (i = 0; i < 1; i++) {
                int err, devno = MKDEV(CBFXS_MAJOR, i);
                err = cdev_add (&cbfxs_devices[i].cdev, devno, 1);
                if (err) {
                        printk(KERN_NOTICE "Error %d adding cbfxs%d", err, i);
                        goto fail;  /* Make this more graceful */
                }
	}

	return 0; /* succeed */

  fail:
	cbfxs_exit();
	return result;
}

/*
 * The cleanup function is used to handle initialization failures as well.
 * Thefore, it must be careful to work correctly even if some of the items
 * have not been initialized
 */
void cbfxs_exit(void)
{
	int i;
	dev_t devno = MKDEV(CBFXS_MAJOR, 0);

	cbfxs_stop_serial_legerity();
	/* Get rid of our char dev entries */
	if (cbfxs_devices) {
		for (i = 0; i < 1; i++) {
			cdev_del(&cbfxs_devices[i].cdev);
		}
                spi_unregister_driver(&cbfxs_spi_driver);
		cbfxs_spi_done(&cbfxs_devices[0]);
		kfree(cbfxs_devices);
	}

	/* cleanup_module is never called if registering failed */
	unregister_chrdev_region(devno, 1);
}

module_init(cbfxs_init);
module_exit(cbfxs_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ACB08 Driver");
MODULE_AUTHOR("Fabio Ferrari");

